package com.javatest.springboot2ratelimit.aop;

import com.google.common.util.concurrent.RateLimiter;
import com.javatest.springboot2ratelimit.annoation.MakeRateLimit;
import com.javatest.springboot2ratelimit.enums.RateLimitFineness;
import com.javatest.springboot2ratelimit.exception.RateLimitException;
import java.lang.reflect.Method;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Created with IntelliJ IDEA.
 * Description:
 *
 * @author lzy
 * @create 2020-07-21 下午3:46
 */
@Aspect
//@Component
public class RateLimitByGuavaAspect extends AbstractRateLimitAspect {
    private static final Logger LOGGER = LoggerFactory.getLogger(RateLimitByGuavaAspect.class);
    /**
     * 用来存放不同接口的RateLimiter(key为类名_接口名称_限速对象，value为RateLimiter)
     */
    private static final ConcurrentHashMap<String, RateLimiter> map = new ConcurrentHashMap<>();

    @Override
    @Before(value = "limitPointCut() && @annotation(rateLimit)", argNames = "joinPoint, rateLimit")
    public void before(JoinPoint joinPoint, MakeRateLimit rateLimit) {
        final Signature signature = joinPoint.getSignature();
        // 完整类名
        final String className = signature.getDeclaringType().getName();
        // 注解所在方法名
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        String methodName = method.getName();

        // 根据不同限流策略控制限流器的配置粒度
        // 默认采用类名_方法名作为key
        final String prefix = className + "_" + methodName;
        String key = prefix;
        if (rateLimit.limitFineness() == RateLimitFineness.IP) {
            key += "_" + getIpAddress();
        } else if (rateLimit.limitFineness() == RateLimitFineness.User) {
            // todo: 这里用一个随机数模拟用户id
            key += "_" + new Random(10).nextInt(5);
        }
        //获取rateLimiter
        if (!map.containsKey(key)) {
            //获取注解每秒加入桶中的token
            double limitNum = (double) rateLimit.limitCount() / rateLimit.limitPeriod();
            map.put(key, RateLimiter.create(limitNum));
        }
        RateLimiter rateLimiter = map.get(key);

        if (!rateLimiter.tryAcquire()) {
            LOGGER.info("[Guava] limiter-{} 对接口访问进行限速", key);
            throw new RateLimitException("访问过于频繁, 拒绝了请求!");
        }
    }
}
